<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Knowledge Graph</title>
    <!-- 离线 Google 字体: Source Sans Pro -->
    <link rel="stylesheet" href="../static/css/google.css?family=Source+Sans+Pro:300,400,400i,700&display=fallback">
    <!-- Font Awesome 图标 -->
    <link rel="stylesheet" href="../static/plugins/fontawesome-free/css/all.min.css">
    <!-- overlayScrollbars -->
    <link rel="stylesheet" href="../static/plugins/overlayScrollbars/css/OverlayScrollbars.min.css">
    <!-- 主题样式 -->
    <link rel="stylesheet" href="../static/css/adminlte.min.css">
    <script src="https://d3js.org/d3.v7.min.js"></script>
    <script src="../static/plugins/jquery/jquery.min.js"></script>
</head>
<body class="hold-transition dark-mode sidebar-mini layout-fixed layout-navbar-fixed layout-footer-fixed">
<div class="wrapper">

  <!-- 导航栏 -->
  <nav class="main-header navbar navbar-expand navbar-dark">
    <!-- 左侧导航栏链接 -->
    <ul class="navbar-nav">
      <li class="nav-item">
        <a class="nav-link" data-widget="pushmenu" href="#" role="button"><i class="fas fa-bars"></i></a>
      </li>
      <li class="nav-item d-none d-sm-inline-block">
        <a href="kgs.html" class="nav-link">系统知识图谱展示</a>
      </li>
    </ul>
  </nav>
  <!-- /.navbar -->

  <!-- 主侧边栏容器 -->
  <aside class="main-sidebar sidebar-dark-primary elevation-4">
    <!-- 品牌 Logo -->
    <a  class="brand-link">
      <img src="../static/img/AdminLTELogo.png" alt="AdminLTE Logo" class="brand-image img-circle elevation-3" style="opacity: .8">
      <span class="brand-text font-weight-light">重力波大脑</span>
    </a>

    <!-- Sidebar -->
    <div class="sidebar">
      <!-- 侧边栏菜单 -->
      <nav class="mt-2">
        <ul class="nav nav-pills nav-sidebar flex-column" data-widget="treeview" role="menu" data-accordion="false">
          <!-- 使用 .nav-icon 类添加图标，
               或使用 font-awesome 或其他任何图标字体库 -->
          <li class="nav-item menu-open">
            <a href="index.html" class="nav-link active">
              <i class="nav-icon fas fa-tachometer-alt"></i>
              <p>
                首页
              </p>
            </a>
          </li>

          <li class="nav-item menu-open">
            <a href="index3.html" class="nav-link active">
              <i class="nav-icon fas fa-tachometer-alt"></i>
              <p>
                气象环境信息
              </p>
            </a>
          </li>

            <li class="nav-item menu-open">
            <a href="kgs.html" class="nav-link active">
              <i class="nav-icon fas fa-tachometer-alt"></i>
              <p>
                系统知识图谱展示
              </p>
            </a>
          </li>

            <li class="nav-item menu-open">
            <a href="index2.html" class="nav-link active">
              <i class="nav-icon fas fa-tachometer-alt"></i>
              <p>
                系统功能展示
              </p>
            </a>
          </li>

          <li class="nav-item menu-open">
            <a href="#" class="nav-link active">
              <i class="nav-icon fas fa-tachometer-alt"></i>
              <p>
                功能交互
                <i class="right fas fa-angle-left"></i>
              </p>
            </a>
            <ul class="nav nav-treeview">
              <li class="nav-item">
                <a href="detect.html" class="nav-link active">
                  <i class="far fa-circle nav-icon"></i>
                  <p>图像受气象干扰影响</p>
                </a>
              </li>
              <li class="nav-item">
                <a href="anoinv.html" class="nav-link active">
                  <i class="far fa-circle nav-icon"></i>
                  <p>重力波反演温度预测</p>
                </a>
              </li>
              <li class="nav-item">
                <a href="generation.html" class="nav-link active">
                  <i class="far fa-circle nav-icon"></i>
                  <p>重力波图像生成</p>
                </a>
              </li>
            </ul>
          </li>
        </ul>
      </nav>
      <!-- /.sidebar-menu -->
    </div>
    <!-- /.sidebar -->
  </aside>

  <!-- Content Wrapper. 包含页面内容 -->
  <div class="content-wrapper">
    <!-- 内容标题（页面标题） -->
    <div class="content-header">
      <div class="container-fluid">
        <div class="row mb-2">
          <div class="col-sm-6">
            <h1 class="m-0">系统知识图谱展示</h1>
          </div>
          <div class="col-sm-6">
            <ol class="breadcrumb float-sm-right">
              <li class="breadcrumb-item"><a href="#">首页</a></li>
              <li class="breadcrumb-item active">知识图谱</li>
            </ol>
          </div>
        </div>
      </div>
    </div>
    <!-- /.content-header -->

    <!-- 主体内容 -->
    <section class="content">
      <div class="container-fluid">
          <div id="graph-container" style="width: 100%; height: 600px;"></div>
      </div>
    </section>
  </div>

  <!-- Control Sidebar -->
  <aside class="control-sidebar control-sidebar-dark">
    <!-- 控制侧边栏内容在这里 -->
  </aside>
</div>
<script>
    let graphData = {
        nodes: [
            {id: "1", name: "重力波温度光谱测试仪", type: "device"},
            {id: "2", name: "重力波同步成像子系统", type: "subsystem"},
            {id: "3", name: "温度光谱检测子系统", type: "subsystem"},
            {id: "4", name: "光谱分光测量子系统", type: "subsystem"},
            {id: "5", name: "CCD相机", type: "component"},
            {id: "6", name: "CCD相机", type: "component"},
            {id: "7", name: "CCD相机", type: "component"},
            {id: "8", name: "滤光轮", type: "component"},
            {id: "9", name: "滤光轮", type: "component"},
            {id: "10", name: "滤光轮", type: "component"},

            // 独立的 TEC 空调
            {id: "11_2", name: "TEC空调", type: "component"}, // 子系统2
            {id: "11_3", name: "TEC空调", type: "component"}, // 子系统3
            {id: "11_4", name: "TEC空调", type: "component"}, // 子系统4

            // 1号CCD相机的属性
            {id: "22_5", name: "性能指标", type: "attribute"},
            {id: "23_5", name: "制造厂商", type: "attribute"},
            {id: "24_5", name: "工作环境", type: "attribute"},

            {id: "22_51", name: "量子效率", type: "data"},
            {id: "23_51", name: "qhyccd", type: "data"},
            {id: "24_51", name: "低温", type: "data"},
            // 2号CCD相机的属性
            {id: "22_6", name: "性能指标", type: "attribute"},
            {id: "23_6", name: "制造厂商", type: "attribute"},
            {id: "24_6", name: "工作环境", type: "attribute"},

            {id: "22_61", name: "量子效率", type: "data"},
            {id: "23_61", name: "qhyccd", type: "data"},
            {id: "24_61", name: "低温", type: "data"},

            // 3号CCD相机的属性
            {id: "22_7", name: "性能指标", type: "attribute"},
            {id: "23_7", name: "制造厂商", type: "attribute"},
            {id: "24_7", name: "工作环境", type: "attribute"},

            {id: "22_71", name: "量子效率", type: "data"},
            {id: "23_71", name: "qhyccd", type: "data"},
            {id: "24_71", name: "低温", type: "data"},
            //状态数据
            {id: "stateData2", name: "状态数据", type: "function", parentId: "2"},
            {id: "temp2", name: "温度", type: "data", parentId: "stateData2", systemId: "1", value: '--'},
            {id: "humi2", name: "相对湿度", type: "data", parentId: "stateData2", systemId: "1", value: '--'},
            {id: "noise2", name: "噪声", type: "data", parentId: "stateData2", systemId: "1", value: '--'},
            {id: "img2", name: "图片", type: "data", parentId: "stateData2"},
            {id: "imgParam2", name: "图片参数", type: "data", parentId: "stateData2"},
            {id: "stateData3", name: "状态数据", type: "function", parentId: "3"},
            {id: "temp3", name: "温度", type: "data", parentId: "stateData3", systemId: "2", value: '--'},
            {id: "humi3", name: "相对湿度", type: "data", parentId: "stateData3", systemId: "2", value: '--'},
            {id: "noise3", name: "噪声", type: "data", parentId: "stateData3", systemId: "2", value: '--'},
            {id: "img3", name: "图片", type: "data", parentId: "stateData3"},
            {id: "imgParam3", name: "图片参数", type: "data", parentId: "stateData3"},
            {id: "stateData4", name: "状态数据", type: "function", parentId: "4"},
            {id: "temp4", name: "温度", type: "data", parentId: "stateData4", systemId: "3", value: '--'},
            {id: "humi4", name: "相对湿度", type: "data", parentId: "stateData4", systemId: "3", value: '--'},
            {id: "noise4", name: "噪声", type: "data", parentId: "stateData4", systemId: "3", value: '--'},
            {id: "img4", name: "图片", type: "data", parentId: "stateData4"},
            {id: "imgParam4", name: "图片参数", type: "data", parentId: "stateData4"},
            {id: "stateData5", name: "状态数据", type: "function", parentId: "5"},
            {id: "temp5", name: "温度", type: "data", parentId: "stateData5", systemId: "1", value: '--'},
            {id: "humi5", name: "相对湿度", type: "data", parentId: "stateData5", systemId: "1", value: '--'},
            {id: "noise5", name: "噪声", type: "data", parentId: "stateData5", systemId: "1", value: '--'},
            {id: "img5", name: "图片", type: "data", parentId: "stateData5"},
            {id: "imgParam5", name: "图片参数", type: "data", parentId: "stateData5"},
            {id: "stateData6", name: "状态数据", type: "function", parentId: "6"},
            {id: "temp6", name: "温度", type: "data", parentId: "stateData6", systemId: "2", value: '--'},
            {id: "humi6", name: "相对湿度", type: "data", parentId: "stateData6", systemId: "2", value: '--'},
            {id: "noise6", name: "噪声", type: "data", parentId: "stateData6", systemId: "2", value: '--'},
            {id: "img6", name: "图片", type: "data", parentId: "stateData6"},
            {id: "imgParam6", name: "图片参数", type: "data", parentId: "stateData6"},
            {id: "stateData7", name: "状态数据", type: "function", parentId: "7"},
            {id: "temp7", name: "温度", type: "data", parentId: "stateData7", systemId: "3", value: '--'},
            {id: "humi7", name: "相对湿度", type: "data", parentId: "stateData7", systemId: "3", value: '--'},
            {id: "noise7", name: "噪声", type: "data", parentId: "stateData7", systemId: "3", value: '--'},
            {id: "img7", name: "图片", type: "data", parentId: "stateData7"},
            {id: "imgParam7", name: "图片参数", type: "data", parentId: "stateData7"},
            {id: "stateData8", name: "状态数据", type: "function", parentId: "8"},
            {id: "temp8", name: "温度", type: "data", parentId: "stateData8", systemId: "1", value: '--'},
            {id: "humi8", name: "相对湿度", type: "data", parentId: "stateData8", systemId: "1", value: '--'},
            {id: "noise8", name: "噪声", type: "data", parentId: "stateData8", systemId: "1", value: '--'},
            {id: "img8", name: "图片", type: "data", parentId: "stateData8"},
            {id: "imgParam8", name: "图片参数", type: "data", parentId: "stateData8"},
            {id: "stateData9", name: "状态数据", type: "function", parentId: "9"},
            {id: "temp9", name: "温度", type: "data", parentId: "stateData9", systemId: "2", value: '--'},
            {id: "humi9", name: "相对湿度", type: "data", parentId: "stateData9", systemId: "2", value: '--'},
            {id: "noise9", name: "噪声", type: "data", parentId: "stateData9", systemId: "2", value: '--'},
            {id: "img9", name: "图片", type: "data", parentId: "stateData9"},
            {id: "imgParam9", name: "图片参数", type: "data", parentId: "stateData9"},
            {id: "stateData10", name: "状态数据", type: "function", parentId: "10"},
            {id: "temp10", name: "温度", type: "data", parentId: "stateData10", systemId: "3", value: '--'},
            {id: "humi10", name: "相对湿度", type: "data", parentId: "stateData10", systemId: "3", value: '--'},
            {id: "noise10", name: "噪声", type: "data", parentId: "stateData10", systemId: "3", value: '--'},
            {id: "img10", name: "图片", type: "data", parentId: "stateData10"},
            {id: "imgParam10", name: "图片参数", type: "data", parentId: "stateData10"},
            {id: "stateData11_2", name: "状态数据", type: "function", parentId: "11_2"},
            {id: "temp11_2", name: "温度", type: "data", parentId: "stateData11_2", systemId: "1", value: '--'},
            {id: "humi11_2", name: "相对湿度", type: "data", parentId: "stateData11_2", systemId: "1", value: '--'},
            {id: "noise11_2", name: "噪声", type: "data", parentId: "stateData11_2", systemId: "1", value: '--'},
            {id: "stateData11_3", name: "状态数据", type: "function", parentId: "11_3"},
            {id: "temp11_3", name: "温度", type: "data", parentId: "stateData11_3", systemId: "2", value: '--'},
            {id: "humi11_3", name: "相对湿度", type: "data", parentId: "stateData11_3", systemId: "2", value: '--'},
            {id: "noise11_3", name: "噪声", type: "data", parentId: "stateData11_3", systemId: "2", value: '--'},
            {id: "stateData11_4", name: "状态数据", type: "function", parentId: "11_4"},
            {id: "temp11_4", name: "温度", type: "data", parentId: "stateData11_4", systemId: "3", value: '--'},
            {id: "humi11_4", name: "相对湿度", type: "data", parentId: "stateData11_4", systemId: "3", value: '--'},
            {id: "noise11_4", name: "噪声", type: "data", parentId: "stateData11_4", systemId: "3", value: '--'},
        ],
        links: [
            {source: "1", target: "2", label: "包含"},
            {source: "1", target: "3", label: "包含"},
            {source: "1", target: "4", label: "包含"},
            {source: "2", target: "5", label: "包含"},
            {source: "2", target: "8", label: "包含"},
            {source: "2", target: "11_2", label: "包含"}, // 连接到子系统2的TEC空调
            {source: "3", target: "6", label: "包含"},
            {source: "3", target: "9", label: "包含"},
            {source: "3", target: "11_3", label: "包含"}, // 连接到子系统3的TEC空调
            {source: "4", target: "7", label: "包含"},
            {source: "4", target: "10", label: "包含"},
            {source: "4", target: "11_4", label: "包含"}, // 连接到子系统4的TEC空调

            // 1号CCD相机的属性连接
            {source: "5", target: "22_5", label: "具有"},
            {source: "5", target: "23_5", label: "具有"},
            {source: "5", target: "24_5", label: "具有"},
            {source: "22_5", target: "22_51", label: "具有"},
            {source: "23_5", target: "23_51", label: "具有"},
            {source: "24_5", target: "24_51", label: "具有"},


            // 2号CCD相机的属性连接
            {source: "6", target: "22_6", label: "具有"},
            {source: "6", target: "23_6", label: "具有"},
            {source: "6", target: "24_6", label: "具有"},
            {source: "22_6", target: "22_61", label: "具有"},
            {source: "23_6", target: "23_61", label: "具有"},
            {source: "24_6", target: "24_61", label: "具有"},

            // 3号CCD相机的属性连接
            {source: "7", target: "22_7", label: "具有"},
            {source: "7", target: "23_7", label: "具有"},
            {source: "7", target: "24_7", label: "具有"},
            {source: "22_7", target: "22_71", label: "具有"},
            {source: "23_7", target: "23_71", label: "具有"},
            {source: "24_7", target: "24_71", label: "具有"},

            {source: "2", target: "stateData2", label: "具有"},
            {source: "stateData2", target: "temp2", label: "包含"},
            {source: "stateData2", target: "humi2", label: "包含"},
            {source: "stateData2", target: "noise2", label: "包含"},
            {source: "stateData2", target: "img2", label: "包含"},
            {source: "stateData2", target: "imgParam2", label: "包含"},
            {source: "3", target: "stateData3", label: "具有"},
            {source: "stateData3", target: "temp3", label: "包含"},
            {source: "stateData3", target: "humi3", label: "包含"},
            {source: "stateData3", target: "noise3", label: "包含"},
            {source: "stateData3", target: "img3", label: "包含"},
            {source: "stateData3", target: "imgParam3", label: "包含"},
            {source: "4", target: "stateData4", label: "具有"},
            {source: "stateData4", target: "temp4", label: "包含"},
            {source: "stateData4", target: "humi4", label: "包含"},
            {source: "stateData4", target: "noise4", label: "包含"},
            {source: "stateData4", target: "img4", label: "包含"},
            {source: "stateData4", target: "imgParam4", label: "包含"},
            {source: "5", target: "stateData5", label: "具有"},
            {source: "stateData5", target: "temp5", label: "包含"},
            {source: "stateData5", target: "humi5", label: "包含"},
            {source: "stateData5", target: "noise5", label: "包含"},
            {source: "stateData5", target: "img5", label: "包含"},
            {source: "stateData5", target: "imgParam5", label: "包含"},
            {source: "6", target: "stateData6", label: "具有"},
            {source: "stateData6", target: "temp6", label: "包含"},
            {source: "stateData6", target: "humi6", label: "包含"},
            {source: "stateData6", target: "noise6", label: "包含"},
            {source: "stateData6", target: "img6", label: "包含"},
            {source: "stateData6", target: "imgParam6", label: "包含"},
            {source: "7", target: "stateData7", label: "具有"},
            {source: "stateData7", target: "temp7", label: "包含"},
            {source: "stateData7", target: "humi7", label: "包含"},
            {source: "stateData7", target: "noise7", label: "包含"},
            {source: "stateData7", target: "img7", label: "包含"},
            {source: "stateData7", target: "imgParam7", label: "包含"},
            {source: "8", target: "stateData8", label: "具有"},
            {source: "stateData8", target: "temp8", label: "包含"},
            {source: "stateData8", target: "humi8", label: "包含"},
            {source: "stateData8", target: "noise8", label: "包含"},
            {source: "stateData8", target: "img8", label: "包含"},
            {source: "stateData8", target: "imgParam8", label: "包含"},
            {source: "9", target: "stateData9", label: "具有"},
            {source: "stateData9", target: "temp9", label: "包含"},
            {source: "stateData9", target: "humi9", label: "包含"},
            {source: "stateData9", target: "noise9", label: "包含"},
            {source: "stateData9", target: "img9", label: "包含"},
            {source: "stateData9", target: "imgParam9", label: "包含"},
            {source: "10", target: "stateData10", label: "具有"},
            {source: "stateData10", target: "temp10", label: "包含"},
            {source: "stateData10", target: "humi10", label: "包含"},
            {source: "stateData10", target: "noise10", label: "包含"},
            {source: "stateData10", target: "img10", label: "包含"},
            {source: "stateData10", target: "imgParam10", label: "包含"},

            //连接tec空调2状态数据
            {source: "11_2", target: "stateData11_2", label: "具有"},
            {source: "stateData11_2", target: "temp11_2", label: "包含"},
            {source: "stateData11_2", target: "humi11_2", label: "包含"},
            {source: "stateData11_2", target: "noise11_2", label: "包含"},

            //连接tec空调3状态数据
            {source: "11_3", target: "stateData11_3", label: "具有"},
            {source: "stateData11_3", target: "temp11_3", label: "包含"},
            {source: "stateData11_3", target: "humi11_3", label: "包含"},
            {source: "stateData11_3", target: "noise11_3", label: "包含"},

            //连接tec空调4状态数据
            {source: "11_4", target: "stateData11_4", label: "具有"},
            {source: "stateData11_4", target: "temp11_4", label: "包含"},
            {source: "stateData11_4", target: "humi11_4", label: "包含"},
            {source: "stateData11_4", target: "noise11_4", label: "包含"},
        ]

    };


    // 初始化画布
    const width = window.innerWidth;
    const height = window.innerHeight;

    const svg = d3.select("#graph-container")
        .append("svg")
        .attr("width", width)
        .attr("height", height);

    //  创建一个 g 元素作为容器
    const g = svg.append("g");

    // 创建力导向图模拟
    const simulation = d3.forceSimulation(graphData.nodes) // 传入节点数据
        .force("link", d3.forceLink(graphData.links).id(d => d.id).distance(50)) // 减小 linkDistance, 更短的线
        .force("charge", d3.forceManyBody().strength(-800)) // 增大 chargeStrength
        .force("center", d3.forceCenter(width / 2, height / 2))
        .alpha(1)          // 初始 alpha 值
        .alphaDecay(0.05)   // 减小 alpha 衰减率
        .alphaTarget(0);    // 目标 alpha 值为 0，让模拟逐渐停止

    // 绘制图形函数
    function updateGraph(data) {
        // 移除旧的链接和节点
        g.selectAll(".link").remove();
        g.selectAll(".node").remove();

        // 更新链接
        const link = g.selectAll(".link")
            .data(data.links)
            .enter()  // 改为 enter().append()，因为selectAll之前已经remove了
            .append("line")
            .attr("class", "link")
            .attr("marker-end", "url(#arrowhead)"); // 添加箭头

        // 定义箭头
        g.append("defs")
            .append("marker")
            .attr("id", "arrowhead")
            .attr("viewBox", "0 -5 10 10")
            .attr("refX", 15) // 箭头偏移量，防止遮挡节点
            .attr("refY", 0)
            .attr("orient", "auto")
            .attr("markerWidth", 6)
            .attr("markerHeight", 6)
            .append("path")
            .attr("d", "M0,-5L10,0L0,5")
            .attr("fill", "#999"); // 箭头颜色

        // 添加链接文字
        link.append("title")
            .text(d => d.label);

        // 更新节点
        const node = g.selectAll(".node")
            .data(data.nodes)
            .enter() // 改为 enter().append()，因为selectAll之前已经remove了
            .append("g")
            .attr("class", d => `node ${d.type}`)
            .call(drag(simulation));


        node.append("circle")
            .attr("r", d => {
                if (d.type === 'device') return 60;
                if (d.type === 'subsystem') return 40;
                if (d.type === 'component') return 30;
                if (d.type === 'attribute') return 20;
                if (d.type === 'function') return 20;
                if (d.type === 'data') return 20;
                return 10;
            })
            .style("fill", d => {
                if (d.type === 'device') return "#2980b9";
                if (d.type === 'subsystem') return "#3498db";
                if (d.type === 'component') return "#85c1e9";
                if (d.type === 'attribute') return "#aed6f1";
                if (d.type === 'function') return "#f39c12";
                if (d.type === 'data') return "#f7dc6f";
                return "#95a5a6";
            });

        // 修改节点文本部分
        node.each(function (d) { // 使用 node.each 遍历每一个 node

            const textElement = d3.select(this)
                .append("text")
                .attr("class", "text")
                .attr("text-anchor", "middle")
                {#.style("fill", "#222222") // 修改文本颜色#}
                .attr("dy", d => {
                    if (d.type === 'device') return 5;
                    if (d.type === 'subsystem') return 4;
                    if (d.type === 'component') return 3;
                    if (d.type === 'attribute') return 2;
                    if (d.type === 'function') return 3;
                    if (d.type === 'data') return 2; // 初始位置，可以根据需要调整
                    return -12;
                });

            if (d.type === 'data' && (d.name === '温度' || d.name === '相对湿度' || d.name === '噪声')) {
                textElement.remove(); // 移除 text 元素
            } else {
                textElement.text(d => d.name); // 设置 text 内容
            }
        });

        // 添加/更新动态数值显示
        node.filter(d => d.type === 'data' && (d.name === '温度' || d.name === '相对湿度' || d.name === '噪声'))
            .selectAll(".value-text") // 选择已经存在的 text 元素
            .data(d => [d]) // 将数据绑定到选择的元素
            .join(
                enter => { // 处理新元素
                    return enter.append("text")
                        .attr("class", "text value-text")
                        .attr("text-anchor", "middle") // 水平居中
                        .attr("dy", d => {
                            if (d.type === 'device') return 5;
                            if (d.type === 'subsystem') return 4;
                            if (d.type === 'component') return 3;
                            if (d.type === 'attribute') return 2;
                            if (d.type === 'function') return 3;
                            if (d.type === 'data') return 2; // 初始位置，可以根据需要调整
                            return -12;
                        }) // 将数值文本垂直居中
                        .text(d => `${d.value || '--'} ${d.unit || ''}`); // 显示 value 和 unit
                },
                update => { // 处理更新的元素
                    return update
                        .text(d => `${d.value || '--'} ${d.unit || ''}`); // 显示 value 和 unit
                },
                exit => { // 处理需要移除的元素
                    return exit.remove();
                }
            );

        // 更新模拟
        simulation.nodes(data.nodes);
        simulation.force("link").links(data.links);
        // simulation.alpha(1).restart();
        simulation.alpha(1).alphaTarget(0).restart(); // 目标 alpha 值为 0
        simulation.alphaDecay(0.05) // 默认 0.0228

        // 定义最大线长
        const maxLinkLength = 140; //  设定你想要的最大长度

        // 设置节点和链接的位置更新
        simulation.on("tick", () => {
            // 遍历所有的链接
            data.links.forEach(link => {
                const source = link.source;
                const target = link.target;

                // 计算当前链接的长度
                const dx = target.x - source.x;
                const dy = target.y - source.y;
                const currentLength = Math.sqrt(dx * dx + dy * dy);

                // 如果超过最大长度
                if (currentLength > maxLinkLength) {
                    // 计算缩放比例
                    const scale = maxLinkLength / currentLength;

                    // 调整目标节点的位置，使其靠近源节点，达到最大长度限制
                    target.x = source.x + dx * scale;
                    target.y = source.y + dy * scale;
                }
            });

            link
                .attr("x1", d => d.source.x)
                .attr("y1", d => d.source.y)
                .attr("x2", d => d.target.x)
                .attr("y2", d => d.target.y);

            node
                .attr("transform", d => `translate(${d.x},${d.y})`);
        });
    }

    // 初始化绘制
    updateGraph(graphData);

    // 拖拽
    function drag(simulation) {

        function dragstarted(event, d) {
            if (!event.active) simulation.alphaTarget(0.3).restart();
            d.fx = d.x;
            d.fy = d.y;
        }

        function dragged(event, d) {
            d.fx = event.x;
            d.fy = event.y;
        }

        function dragended(event, d) {
            if (!event.active) simulation.alphaTarget(0);
            d.fx = null;
            d.fy = null;
        }

        return d3.drag()
            .on("start", dragstarted)
            .on("drag", dragged)
            .on("end", dragended);
    }

    // 缩放和平移
    const zoom = d3.zoom()
        .scaleExtent([0.1, 5]) // 缩放范围
        .on("zoom", function (event) {
            // svg.attr("transform", event.transform); //  错误，应该使用g
            g.attr("transform", event.transform); //  将变换应用到容器 g
        });

    svg.call(zoom);

    // API调用
    function fetchDataAndUpdateGraph() {
        $.ajax({
            url: '/api/kgs/system_data',
            type: 'GET',
            dataType: 'json',
           success: function(response) {
            // 严格匹配字段命名
            response.forEach(sensor => {
                graphData.nodes.forEach(node => {
                    if (node.type === 'data' &&
                        node.name === sensor.name &&
                        node.systemId === sensor.system_id) { // 保持Java字段命名
                        node.value = sensor.value !== null ?
                            Number(sensor.value.toFixed(2)) :
                            '--';
                        node.unit = sensor.unit;
                    }
                });
            });


                // 重新绘制
                updateGraph(graphData);
            },
            error: function (xhr, status, error) {
                console.error('获取传感器数据失败:', error);
                console.error('HTTP status code:', xhr.status);
            }
        });
    }

    setInterval(fetchDataAndUpdateGraph, 3000);//刷新间隔为3秒
</script>
</body>
</html>
<style>
    body {
        overflow: hidden;
        background-color: #d3d3d3;
    }

    .node {
        cursor: pointer;
    }

    .node.core {
        fill: #4CAF50;
    }

    .node.category {
        fill: #2196F3;
    }

    .node.sensor {
        fill: #FF9800;
    }

    .link {
        stroke: #000b16;
        stroke-opacity: 0.6;
    }

    .text {
        font-family: Arial;
        font-size: 12px;
        fill: #222222;
        pointer-events: none;
    }

    .value-text {
        font-weight: bold;
        font-size: 14px;
    }
</style>